"""
	The MainController module defines the controller classes for the HeaDSET
	wxPython GUI components. All sub-Controllers in this module have Controller
	as superclass.

	@author: J.H.Donker, T.L. Siaw
	@organization: University of Twenty
"""

# ==================== Importing external libraries

import os, wx, shutil
from Help import *
from ConfigureViewports import *
from model import *
from peripherals import *
from util import *

from wx.lib.pubsub import Publisher
from FloatSpinner import *

# ==================== Controller classes for the views

class Controller:
        """
		Controller is the superclass of the different controllers of
		the HeaDSET program. It provides the common features and event
		handling mechanisms for the corresponding views.
	"""

	def __init__(self, model, view):
                """
                    The constructor for Controller initialises the main variabels needed to run.
                    
                    @param model: The model belonging to this GUI.
                    @param view: The view where events are handled from.
                """

		self.model = model
		self.view = view
		self.saver = ArchiveSaver()
		self.programPath = os.getcwd()
        
	def BindEvents(self):
                """
                    This method binds the events to the common widgets of the View.
                    In this case some menu items under the menu File, Viewports and Help.
                """

                self.view.Bind(wx.EVT_MENU, self.OnExit , id = self.view.FILE_EXIT)
		self.view.Bind(wx.EVT_MENU, self.OnAbout, id = self.view.HELP_ABOUT)
		self.view.Bind(wx.EVT_MENU, self.OnHelp , id = self.view.HELP_HELP)

		self.view.Bind(wx.EVT_MENU, self.OnSingleView, id = self.view.SINGLE_VIEW)
		self.view.Bind(wx.EVT_MENU, self.OnMultiView , id = self.view.MULTI_VIEW)
		self.view.Bind(wx.EVT_MENU, self.OnConfigView, id = self.view.CONFIG_VIEW)		

	# -------------------- Methods for handling events from the menubar
							
	def OnExit(self, event):
                """
                    Method for handling an incoming event to exit the program.
                    
                    @param event: Incoming event from view.
                """

		self.view.Close()

	def OnAbout(self, event):
                """
                    Method for handling an incoming event to show background information
                    about the HeaDSET program.
                    
                    @param event: Incoming event from view.
                """

                utLogo = wx.Icon(os.path.join(self.programPath, "res/UTLogo.gif"), wx.BITMAP_TYPE_GIF)

		descriptionText = self.GetAboutDoc()

		licenseTextPath = os.path.join(self.programPath, "res/HeaDSETLicense.txt")
		licenseText = self.GetTextFromFile(licenseTextPath)

		infoDialog = wx.AboutDialogInfo()
		infoDialog.SetIcon(utLogo)
		infoDialog.SetName(self.view.GetTitle())
		infoDialog.SetVersion("1.0")
		infoDialog.SetDescription(descriptionText)
		infoDialog.SetCopyright(u"\u00A9 2009 University of Twente")
		infoDialog.SetWebSite("http://www.utwente.nl")
		infoDialog.SetLicense(licenseText)
		infoDialog.AddDeveloper("John Donker")
		infoDialog.AddDeveloper("Simon Epskamp")
		infoDialog.AddDeveloper("Jordi Hendrix")
		infoDialog.AddDeveloper("Tien-Loong Siaw")
		infoDialog.AddDeveloper("Jalmar Teeuw")

		wx.AboutBox(infoDialog)

	def OnHelp(self, event):
                """
                    Method for handling an incoming event to show the help information
                    of the HeaDSET program.
                    
                    @param event: Incoming event from view.
                """

		HelpWindow(self.view, -1, "HeaDSET Help documentation", self.programPath)

	def OnSingleView(self, event):
                """
                    Method for handling an incoming event to change the view to a single viewport.
                    
                    @param event: Incoming event from view.
                """

                vtkModelPanel = self.view.FindWindowById(self.view.VISUALIZER)
		vtkModelPanel.SetSingleViewport()
	
	def OnMultiView(self, event):
                """
                    Method for handling an incoming event to change the view to a multiple viewport.
                    
                    @param event: Incoming event from view.
                """

                vtkModelPanel = self.view.FindWindowById(self.view.VISUALIZER)
		vtkModelPanel.SetMultipleViewports()
		
	def OnConfigView(self, event):
                """
                    Method for handling an incoming event to change the view to a single viewport.
                    
                    @param event: Incoming event from view.
                """

		cvDialog = ConfigureViewports(self.view)
		cvDialog.ShowModal()

	# -------------------- Auxiliary methods

	def GetTextFromFile(self, filePath):
                """
                    Method for extracting textual information from a textfile.
                    
                    @param filePath: The path of the textfile.
                    @return: String extracted from file.
                """

		infoFile = open(filePath, "r")
		infoText = infoFile.read()
		infoFile.close()
		return infoText

	def GetAboutDoc(self):
                """
                    Method for returning backgroundinformation about the HeaDSET program.

                    @return: String with information about the program.
                """

                aboutTextPath = os.path.join(self.programPath, "res/HeaDSETInfo.txt")
                return self.GetTextFromFile(aboutTextPath)

# -------------------- End of class Controller



class CommonController(Controller):
        """
		CommonController is a derived class from Controller and provides additional
		common features and event handling mechanisms for specific views.
	"""

	def __init__(self, model, view):
                """
                    The constructor for CommonController initialises the main variabels needed to run.
                    
                    @param model: The model belonging to this GUI.
                    @param view: The view where events are handled from.
                """

		Controller.__init__(self, model, view)
		self.InitProjectSettings()
		self.explanations = {}

	def BindEvents(self):
                """
                    This method binds the events to the common widgets of specific views.
                    In this case some menu items under the menu File, regarding opening and saving.
                """

		Controller.BindEvents(self)

		self.view.Bind(wx.EVT_CLOSE, self.OnClosingFrame)
		
		self.view.Bind(wx.EVT_MENU, self.OnOpen  , id = self.view.FILE_OPEN)
		self.view.Bind(wx.EVT_MENU, self.OnSave  , id = self.view.FILE_SAVE)
		self.view.Bind(wx.EVT_MENU, self.OnSaveAs, id = self.view.FILE_SAVEAS)
		self.view.Bind(wx.EVT_MENU, self.OnProp  , id = self.view.FILE_PROP)

	def InitProjectSettings(self):
                """
                    This method initialises some variables in case a new project is openend.
                """

		# Open a TMP file as saveFile
		self.saver.Reset()
		self.modified = False

	# -------------------- Methods for handling events from the frame window

	def OnClosingFrame(self, event):
                """
                    Method for handling an incoming event for closing the program.
                    
                    @param event: Incoming event from view.
                """

		if self.modified:
			msg = "Do you want to save changes to this project before closing project?"
			title = "Save on close"
			options = wx.YES_NO | wx.YES_DEFAULT | wx.CANCEL | wx.ICON_QUESTION
			dlg = wx.MessageDialog(self.view, msg, title, options)
			choice = dlg.ShowModal()
			if choice == wx.ID_YES:
				self.OnSave(event)
				if not self.modified:
					wx.Exit()
			elif choice == wx.ID_NO:
				wx.Exit()
		else:
			wx.Exit()

	# -------------------- Methods for handling events from moving cursor

	def OnEnterPanel(self, event):
                """
                    Method for handling an incoming event to show explanatory text in
                    explanantion panel (in the view).
                    
                    @param event: Incoming event from view.
                """

		message = self.explanations[event.GetId()]
		self.view.ShowExplanation(message)

	# -------------------- Methods for handling events from the menubar

	def OnOpen(self, event):
                """
                    Method for handling an incoming event to open another project.
                    
                    @param event: Incoming event from view.
                """

		if self.modified:
			msg = "Do you want to save changes to this project before opening another project?"
			title = "Save before open"
			options = wx.YES_NO | wx.YES_DEFAULT | wx.CANCEL | wx.ICON_QUESTION
			dlg = wx.MessageDialog(self.view, msg, title, options)
			choice = dlg.ShowModal()
			if choice == wx.ID_YES:
				self.OnSave(event)
				if not self.modified:
					self.OpenProject()
			elif choice == wx.ID_NO:
				self.OpenProject()
		else:
			self.OpenProject()

	def OnSave(self, event):
                """
                    Method for handling an incoming event to save the current project.
                    
                    @param event: Incoming event from view.
                """

		if not self.saver.IsTemp():
			self.modified = False
			self._Save()
		else:
			self.OnSaveAs(event)

	def OnSaveAs(self, event):
                """
                    Method for handling an incoming event to save the current project
                    under another filename.
                    
                    @param event: Incoming event from view.
                """

		title = "Save Project As"
		currentDir = os.getcwd()
		extensions = "HeaDSET project files (*.hdt)|*.hdt| All files (*)|*"
		styles = wx.SAVE | wx.CHANGE_DIR | wx.OVERWRITE_PROMPT

		saveDialog = wx.FileDialog(self.view, title, currentDir, "", extensions, styles)
		if saveDialog.ShowModal() == wx.ID_OK:
			newPath = saveDialog.GetPath()
			
			# Delete an old file if overwriting it
			if os.path.exists(newPath):
				os.unlink(newPath)
			self.saver.SetSavePath(newPath)

			self.modified = False
			self._Save()

	def OnProp(self, event):
                """
                    Method for handling an incoming event to get property information
                    about the current project.
                    
                    @param event: Incoming event from view.
                """

		propDialog = PropertiesDialog(self.view, -1, "Properties of project", self.model)
		propDialog.ShowModal()

	# -------------------- Auxiliary methods

	def OpenProject(self):
                """
                    Method for opening another project by providing a dialog window
                    for selecting the HeaDSET projectfile.
                """

                self.InitProjectSettings()
		title = "Open Project"
		currentDir = os.getcwd()
		extensions = "HeaDSET project files (*.hdt)|*.hdt| All files (*)|*"
		styles = wx.OPEN | wx.CHANGE_DIR

		openDialog = wx.FileDialog(self.view, title, currentDir, "", extensions, styles)
		if openDialog.ShowModal() == wx.ID_OK:
			self.saver.SetSavePath(openDialog.GetPath())
			self.modified = False
			
			# Load XML from TAR file
			self.model.Load(self.saver.LoadXML())
			
			# Load DICOM files from TAR file
			if self.saver.LoadDicom():
				self._LoadDicom(os.path.join(self.programPath, "tmp", "dicom"))
			
			recordings = self.saver.ListRecordings()
			if self.model.IsModelMode() and len(recordings) > 0:
				wx.MessageBox("File contains recordings, please save a copy without recordings","HeaDSET modelling warning")
				self.saver = ArchiveSaver()
				self.OnSaveAs(None)
			else:	
				for record in recordings:
					recordingcsv = self.saver.LoadRecording(record)
					recording = Recording()
					recording.Load(recordingcsv)
					self.model.AddRecording(recording)

	def _Save(self):
                """
                    Method for saving the current project.
                """

		self.saver.SaveXML(self.model.Save())
		for i in range(0, self.model.GetNumberOfRecordings()):
			print "saving record " + str(i)
			record = self.model.GetRecording(i)
			self.saver.AddRecording(record.ToString(), "rec_" + str(i) + ".csv")

	def _LoadDicom(self, path):
                """
                    Method for loading a DICOM image for 3D model creation. A dialog window is opened
                    to change any properties regarding calculation of the 3D model from the DICOM image.

                    @param path: The path of the directory containing the DICOM images.
                    @return: True if loading DICOM image has succeeded, otherwise false.
                """

		dialog = DicomDialog(self.view, -1, "Dicom")
		dialog.ShowModal()
			
		dicom = HeaDSETDicom(path, "Dicom",  dialog.GetExtraction(), dialog.GetDecimation(), dialog.GetSmoothing(), dialog.GetColor(), dialog.GetOpacity())
		dialog.Destroy()
		if not dicom.IsFailed():
			self.model.SetDicomModel(dicom)
		else:
			wx.MessageBox("The given directory does not contain Dicom data.", "Dicom failed")
		return not dicom.IsFailed()

# -------------------- End of class CommonController



class ModelingController(CommonController):
        """
		ModelingController is a derived class from CommonController and provides
		specific features and event handling mechanisms for ModelingView.
	"""

	def __init__(self, model, view):
                """
                    The constructor for ModelingController initialises the main variabels needed to run.
                    
                    @param model: The model belonging to this GUI.
                    @param view: The view where events are handled from.
                """

		CommonController.__init__(self, model, view)

		helpMod = self.GetTextFromFile(os.path.join(self.programPath, "res/ModelExplanation.txt"))
		helpPos = self.GetTextFromFile(os.path.join(self.programPath, "res/PositionExplanation.txt"))
		self.explanations[self.view.MOD_PANEL] = helpMod
		self.explanations[self.view.POS_PANEL] = helpPos

		self.selectedModTab = HeaDSETModel.TUMOR
		self.__pickedGeom = None

		self.BindEvents()

		Publisher.subscribe(self.BindInfoLabel, "BIND_LABEL")
		Publisher.subscribe(self.OnPick       , "GeomPicked")
		Publisher.subscribe(self.OnUpdate     , "GeomUpdated")

	def BindEvents(self):
                """
                    This method binds the events to the widgets of ModelingView.
                """

		CommonController.BindEvents(self)

		self.view.Bind(wx.EVT_MENU, self.OnNew   , id = self.view.FILE_NEW)
		self.view.Bind(wx.EVT_MENU, self.OnImport, id = self.view.FILE_IMPORT)

		miPanel = self.view.FindWindowById(self.view.MOD_PANEL)
		piPanel = self.view.FindWindowById(self.view.POS_PANEL)
		miPanel.Bind(wx.EVT_ENTER_WINDOW, self.OnEnterPanel, id = self.view.MOD_PANEL)
		piPanel.Bind(wx.EVT_ENTER_WINDOW, self.OnEnterPanel, id = self.view.POS_PANEL)

		self.view.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnChangeModTabs, id = self.view.MOD_TABS)

		self.view.Bind(wx.EVT_BUTTON, self.OnBox      , id = self.view.BO_BUTTON)
		self.view.Bind(wx.EVT_BUTTON, self.OnEllipsoid, id = self.view.EL_BUTTON)
		self.view.Bind(wx.EVT_BUTTON, self.OnCylinder , id = self.view.CY_BUTTON)
		self.view.Bind(wx.EVT_BUTTON, self.OnCone     , id = self.view.CO_BUTTON)
		
		# Binding events to spin controls for scaling
		self.view.Bind(FloatSpinner.EVT_FPSC_VALUE_CHANGED, self.OnScale, id = self.view.X_SPIN)
		self.view.Bind(FloatSpinner.EVT_FPSC_VALUE_CHANGED, self.OnScale, id = self.view.Y_SPIN)
		self.view.Bind(FloatSpinner.EVT_FPSC_VALUE_CHANGED, self.OnScale, id = self.view.Z_SPIN)

		# Binding events to spin controls for moving
		self.view.Bind(FloatSpinner.EVT_FPSC_VALUE_CHANGED, self.OnMove, id = self.view.X_SPIN + self.view.NR_MOVE)
		self.view.Bind(FloatSpinner.EVT_FPSC_VALUE_CHANGED, self.OnMove, id = self.view.Y_SPIN + self.view.NR_MOVE)
		self.view.Bind(FloatSpinner.EVT_FPSC_VALUE_CHANGED, self.OnMove, id = self.view.Z_SPIN + self.view.NR_MOVE)

		# Binding events to spin controls for rotation
		self.view.Bind(FloatSpinner.EVT_FPSC_VALUE_CHANGED, self.OnRotate, id = self.view.X_SPIN + self.view.NR_ROTATE)
		self.view.Bind(FloatSpinner.EVT_FPSC_VALUE_CHANGED, self.OnRotate, id = self.view.Y_SPIN + self.view.NR_ROTATE)
		self.view.Bind(FloatSpinner.EVT_FPSC_VALUE_CHANGED, self.OnRotate, id = self.view.Z_SPIN + self.view.NR_ROTATE)

		self.view.Bind(wx.EVT_BUTTON, self.OnClone , id = self.view.CLO_BUTTON)
		self.view.Bind(wx.EVT_BUTTON, self.OnDelete, id = self.view.DEL_BUTTON)
		self.view.Bind(wx.EVT_BUTTON, self.OnClear , id = self.view.CLR_BUTTON)

	def StartNewProject(self):
		self.model.Reset()
		self.InitProjectSettings()
		self.view.StartNew()

	# -------------------- Methods for handling events from the menubar

	def OnNew(self, event):
                """
                    Method for handling an incoming event to open a new project.
                    
                    @param event: Incoming event from view.
                """

		infoMsg = "Current project will be closed. "
		if self.modified:
			msg = infoMsg + "Do you want to save changes to this project before starting a new project?"
			title = "Save on new"
			options = wx.YES_NO | wx.YES_DEFAULT | wx.CANCEL | wx.ICON_QUESTION
			dlg = wx.MessageDialog(self.view, msg, title, options)
			choice = dlg.ShowModal()
			if choice == wx.ID_YES:
				self.onSave(event)
				if not self.modified:
					self.StartNewProject()
					#self.view.StartNew()
			elif choice == wx.ID_NO:
				self.StartNewProject()
		else:
			msg = infoMsg + "Do you want to continue creating a new project?"
			title = "New on close"
			options = wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION
			dlg = wx.MessageDialog(self.view, msg, title, options)
			if dlg.ShowModal() == wx.ID_YES:
				self.StartNewProject()

	def OnImport(self, event):
                """
                    Method for handling an incoming event to import a DICOM image.
                    
                    @param event: Incoming event from view.
                """

		# Test if saveFile contains a DICOM image
		if self.saver.HasDicom():
			msg = "Project already contains a DICOM model. Do you want to replace the current DICOM model with a new one?"
			title = "Import in project with DICOM"
			options = wx.YES_NO | wx.YES_DEFAULT | wx.CANCEL | wx.ICON_QUESTION
			dlg = wx.MessageDialog(self.view, msg, title, options)
			choice = dlg.ShowModal()
			if choice == wx.ID_YES:
				self.model.UnsetDicomModel()
				
				# Create a new file, since removing file from TAR archive is impossible with Python TAR file module
				self.saver.Delete();
				self._Save()
				self._Import()
		else:
			self._Import()

	# -------------------- Methods for handling events from user input

	def OnChangeModTabs(self, event):
                """
                    Method for handling an incoming event when a tab has changed
                    when adding a primitive (tumor or safe path).
                    
                    @param event: Incoming event from view.
                """

		tab = event.GetEventObject()
		self.selectedModTab = tab.GetPageText(tab.GetSelection())

	def OnBox(self, event):
                """
                    Method for handling an incoming event when the [Box] button is pressed.
                    
                    @param event: Incoming event from view.
                """

		self._addToModel(HeaDSETCube())

	def OnEllipsoid(self, event):
                """
                    Method for handling an incoming event when the [Ellipsoid] button is pressed.
                    
                    @param event: Incoming event from view.
                """

		self._addToModel(HeaDSETSphere())

	def OnCylinder(self, event):
                """
                    Method for handling an incoming event when the [Cylinder] button is pressed.
                    
                    @param event: Incoming event from view.
                """

		self._addToModel(HeaDSETCylinder())

	def OnCone(self, event):
                """
                    Method for handling an incoming event when the [Cone] button is pressed.
                    
                    @param event: Incoming event from view.
                """

		self._addToModel(HeaDSETCone())
		
        def OnClone(self, event):
                """
                    Method for handling an incoming event when the [Clone selection] button is pressed.
                    
                    @param event: Incoming event from view.
                """

                if self.__pickedGeom != None:
                        self.model.Clone(self.__pickedGeom)

	def OnDelete(self, event):
                """
                    Method for handling an incoming event when the [Delete selection] button is pressed.
                    
                    @param event: Incoming event from view.
                """

		self.model.RemoveGeom(self.__pickedGeom)
		self.__pickedGeom = None

	def OnClear(self, event):
                """
                    Method for handling an incoming event when the [Clear selection] button is pressed.
                    
                    @param event: Incoming event from view.
                """

		Publisher.sendMessage("GeomPicked", None)

	def OnLabelSelection(self, event):
                """
                    Method for handling an incoming event when a label is clicked on.
                    
                    @param event: Incoming event from view.
                """

		selection = event.GetEventObject()
		geomID = selection.GetName()
		geom = self.model.GetGeomByID(int(geomID))

		if geom in self.model.GetMutables() and selection.GetBackgroundColour() != "NAVY":
			self.view.SettingStatusBar("", "")
			Publisher.sendMessage("GeomPicked", geom)

	def OnScale(self, event):
                """
                    Method for handling an incoming event when a scale value has been changed
                    in one of the spin controls concerning scale.
                    
                    @param event: Incoming event from view.
                """

		xSpin = self.view.FindWindowById(self.view.X_SPIN)
		ySpin = self.view.FindWindowById(self.view.Y_SPIN)
		zSpin = self.view.FindWindowById(self.view.Z_SPIN)
		
		x = xSpin.GetValue()
		y = ySpin.GetValue()
		z = zSpin.GetValue()
		
		self.__pickedGeom.SetScale(x, z, y)
		
	def OnMove(self, event):
                """
                    Method for handling an incoming event when a move value has been changed
                    in one of the spin controls concerning move.
                    
                    @param event: Incoming event from view.
                """

		xSpin = self.view.FindWindowById(self.view.X_SPIN + self.view.NR_MOVE)
		ySpin = self.view.FindWindowById(self.view.Y_SPIN + self.view.NR_MOVE)
		zSpin = self.view.FindWindowById(self.view.Z_SPIN + self.view.NR_MOVE)
		
		x = xSpin.GetValue()
		y = ySpin.GetValue()
		z = zSpin.GetValue()
		
		self.__pickedGeom.SetPosition(x, z, y)
		
	def OnRotate(self, event):
                """
                    Method for handling an incoming event when a rotation value has been changed
                    in one of the spin controls concerning rotation.
                    
                    @param event: Incoming event from view.
                """

		xSpin = self.view.FindWindowById(self.view.X_SPIN + self.view.NR_ROTATE)
		ySpin = self.view.FindWindowById(self.view.Y_SPIN + self.view.NR_ROTATE)
		zSpin = self.view.FindWindowById(self.view.Z_SPIN + self.view.NR_ROTATE)
		
		x = xSpin.GetValue()
		y = ySpin.GetValue()
		z = zSpin.GetValue()
		
		self.__pickedGeom.SetOrientation(x, z, y)

	# -------------------- Auxiliary methods

	def _Import(self):
                """
                    Method for importing a DICOM image.
                """

		msg = "Import a DICOM file:"
		styles = wx.DD_DIR_MUST_EXIST | wx.DD_CHANGE_DIR

		importDialog = wx.DirDialog(self.view, msg, os.getcwd(), styles)
		if importDialog.ShowModal() == wx.ID_OK:
			path = importDialog.GetPath()
			if self._LoadDicom(path):
                                # Add DICOM data to save file
				self.saver.AddDicom(path)
				self.modified = True

	def _addToModel(self, geom):
                """
                    Method for adding a given primitive to the 3D model.

                    @param geom: The type of primitive to be added to the model.
                """

		Publisher.sendMessage("GeomPicked", None)
		if self.selectedModTab == HeaDSETModel.TUMOR:
			self.model.AddToTumor(geom)
		else:
			self.model.AddToPath(geom)
		self.modified = True

	def GetAboutDoc(self):
                """
                    Method for returning backgroundinformation about HeaDSET PathCreator.

                    @return: String with information about the program.
                """

                endText = Controller.GetAboutDoc(self)
                aboutTextPath = os.path.join(self.programPath, "res/HeaDSETCreator.txt")
                resultText = self.GetTextFromFile(aboutTextPath) + "\n" + endText
                return resultText

        # -------------------- Publisher-Subscriber methods

	def BindInfoLabel(self, message):
                """
                    Method indicating that a new label has been added to the view and
                    bind the corresponding event to the label.
                    
                    @param message: The data provided to this ModelingController to make
                    the necessary changes possible.
                """

		newLabel = message.data
		newLabel.Bind(wx.EVT_LEFT_DOWN, self.OnLabelSelection)

	def OnPick(self, message):
                """
                    Method indicating that something is picked in the view,
                    either a primitive or empty space. It is used to update the view.
                    
                    @param message: The data provided to this ModelingController to make
                    the necessary changes possible.
                """

		geom = message.data
		self.__pickedGeom = geom
		if geom != None:
			typeText = HeaDSETModel.TUMOR
			primText = geom.GetLabel() + " " + str(geom.GetID())
			if not self.model.IsTumorGeom(geom):
				typeText = HeaDSETModel.PATH
			textLabel = self.view.GetTabLabel(geom)
			if self.view.EnableInfoLabel(textLabel, typeText, primText):
				self.view.UpdatePos(geom, False)
		else:
			self.view.EnableInfoLabel(None, "", "")

	def OnUpdate(self, message):
                """
                    Method indicating that an update is needed after a change is provided
                    to the model concerning the properties of a primitive.
                    
                    @param message: The data provided to this ModelingController to make
                    the necessary changes possible.
                """

		geom = message.data
		if geom == self.__pickedGeom:
			self.view.UpdatePos(geom, True)

# -------------------- End of class ModelingController



class OperationController(CommonController):
        """
		OperationController is a derived class from CommonController and provides
		specific features and event handling mechanisms for OperationView.
	"""

	def __init__(self, model, view):
                """
                    The constructor for OperationController initialises the main variabels needed to run.
                    
                    @param model: The model belonging to this GUI.
                    @param view: The view where events are handled from.
                """

		CommonController.__init__(self, model, view)

		helpVis = self.GetTextFromFile(os.path.join(self.programPath, "res/VisibilityExplanation.txt"))
		helpRec = self.GetTextFromFile(os.path.join(self.programPath, "res/RecordingExplanation.txt"))
		self.explanations[self.view.VIS_PANEL] = helpVis
		self.explanations[self.view.REC_PANEL] = helpRec
		
		self.__timer = wx.Timer(None, -1)
		self.__time = 0
		
		self.BindEvents()

	def BindEvents(self):
                """
                    This method binds the events to the widgets of OperationView.
                """

		CommonController.BindEvents(self)
		
		self.view.Bind(wx.EVT_MENU, self.OnToolPosition, id = self.view.TOOL_POS)
		self.view.Bind(wx.EVT_MENU, self.OnToolJoystick, id = self.view.TOOL_JOY)
		self.view.Bind(wx.EVT_MENU, self.OnToolNone    , id = self.view.TOOL_NONE)

		siPanel = self.view.FindWindowById(self.view.VIS_PANEL)
		rePanel = self.view.FindWindowById(self.view.REC_PANEL)
		siPanel.Bind(wx.EVT_ENTER_WINDOW, self.OnEnterPanel, id = self.view.VIS_PANEL)
		rePanel.Bind(wx.EVT_ENTER_WINDOW, self.OnEnterPanel, id = self.view.REC_PANEL)

		self.view.Bind(wx.EVT_CHECKBOX, self.OnHead    , id = self.view.HE_CHECKBOX)
		self.view.Bind(wx.EVT_CHECKBOX, self.OnTumor   , id = self.view.TU_CHECKBOX)
		self.view.Bind(wx.EVT_CHECKBOX, self.OnSafePath, id = self.view.SP_CHECKBOX)
		self.view.Bind(wx.EVT_CHECKBOX, self.OnTool    , id = self.view.TO_CHECKBOX)

		self.view.Bind(wx.EVT_BUTTON, self.OnRecording, id = self.view.REC_BUTTON)
		self.__timer.Bind(wx.EVT_TIMER, self.OnTick)
		self.model.DisplayInstrument()
		
		self.inputController = None
		self.outputController = OutputInstrumentRecording()

	def ChangeInputController(self, newController):
                """
                    Method for changing the imput controller, depending on the type of
                    input tool is selected for recording.
                    
                    @param newController: The input controller to change to.
                """

		if self.inputController == None:
			self.inputController = newController
			self.inputController.Start()
		else:
			self.inputController.Stop()
			self.inputController = None
			self.ChangeInputController(newController)

	# -------------------- Methods for handling events from the frame window

	def OnClosingFrame(self, event):
                """
                    Method for handling an incoming event for closing the program.
                    
                    @param event: Incoming event from view.
                """

                recButton = self.view.FindWindowById(self.view.REC_BUTTON)
                if recButton.GetLabel() == "Stop recording":
                        msg = "Program cannot be closed, because it is busy with a recording! Stop the recording first to close the program."
			title = "Close while busy recording"
			options = wx.OK | wx.ICON_ERROR
			dlg = wx.MessageDialog(self.view, msg, title, options)
			dlg.ShowModal()
                else:
                        CommonController.OnClosingFrame(self, event)

        # -------------------- Methods for handling events from a timer

	def OnTick(self, event):
                """
                    Method for handling an incoming event to update the timer.
                    
                    @param event: Incoming event from view.
                """

		seconds = self.__time % 60
		minute =  (self.__time / 60 ) % (60 * 60)
		hours = self.__time / (60 * 60)
		
		if  (hours < 10):
			time = "0" + str(hours) 
		else:
			time = str(hours)
		
		if (minute < 10):
			time = time + " : 0" + str(minute)
		else:
			time = time + " : " + str(minute)
		
		if (seconds < 10):
			time = time + " : 0" + str(seconds)
		else:
			time = time + " : " + str(seconds)

		self.view.UpdateTimer(time)
		self.__time = self.__time + 1

	# -------------------- Methods for handling events from the menubar

	def OnToolNone(self, event):
                """
                    Method for handling an incoming event when [None] is clicked
                    under menu Input Tools.
                    
                    @param event: Incoming event from view.
                """

		if self.inputController != None:
			self.inputController.Stop()
			self.inputController = None
					
	def OnToolPosition(self, event):
                """
                    Method for handling an incoming event when [Position Instrument]
                    is clicked under menu Input Tools.
                    
                    @param event: Incoming event from view.
                """

		wrapper = InputWrapper(self.model)
		ps = PositionController(wrapper)
		self.ChangeInputController(ps)
			
	def OnToolJoystick(self, event):
                """
                    Method for handling an incoming event when [Joystick controller]
                    is clicked under menu Input Tools.
                    
                    @param event: Incoming event from view.
                """

		wrapper = InputWrapper(self.model)
		js = JoystickController(wrapper)
		self.ChangeInputController(js)

	# -------------------- Methods for handling events from user input
		
	def OnHead(self, event):
                """
                    Method for handling an incoming event when the [Head] checkbox is clicked.
                    
                    @param event: Incoming event from view.
                """

		self.model.SetVisibleGroup("Dicom", event.IsChecked())

	def OnTumor(self, event):
                """
                    Method for handling an incoming event when the [Tumor] checkbox is clicked.
                    
                    @param event: Incoming event from view.
                """

		self.model.SetVisibleGroup("Tumor", event.IsChecked())

	def OnSafePath(self, event):
                """
                    Method for handling an incoming event when the [Safe path] checkbox is clicked.
                    
                    @param event: Incoming event from view.
                """

		self.model.SetVisibleGroup("Path", event.IsChecked())

	def OnTool(self, event):
                """
                    Method for handling an incoming event when the [Tool] checkbox is clicked.
                    
                    @param event: Incoming event from view.
                """

		self.model.SetVisibleGroup("Instrument", event.IsChecked())

	def OnRecording(self, event):
                """
                    Method for handling an incoming event when the recording button is pressed.
                    
                    @param event: Incoming event from view.
                """

		recButton = event.GetEventObject()
		if recButton.GetLabel() == "Start recording":
			recButton.SetLabel("Stop recording")
			count = self.model.GetNumberOfRecordings() + 1
			self.view.ShowRecName(count)
			self.__time = 1
			self.__timer.Start(1000)
			self.view.EnableMenu(False)
			self.view.SettingStatusBar("Busy recording...", "")
			Publisher.sendMessage("InstrumentStart", self.model.GetInstrument())
		      
		else:
			recButton.SetLabel("Start recording")
			self.view.ShowRecName(-1)
			self.__timer.Stop()
			self.modified = True

			self.view.SettingStatusBar("Finished with Recording ", "")
			self.view.EnableMenu(True)
			Publisher.sendMessage("InstrumentStop", self.model.GetInstrument())
			record = self.outputController.GetRecording()
			self.model.AddRecording(record)
			self.view.UpdateTimer("00 : 00 : 00")

        # -------------------- Auxiliary methods

	def GetAboutDoc(self):
                """
                    Method for returning backgroundinformation about HeaDSET PathRecorder.

                    @return: String with information about the program.
                """

                endText = Controller.GetAboutDoc(self)
                aboutTextPath = os.path.join(self.programPath, "res/HeaDSETRecorder.txt")
                resultText = self.GetTextFromFile(aboutTextPath) + "\n" + endText
                return resultText

# -------------------- End of class OperationController



class PlayerController(CommonController):
        """
		PlayerController is a derived class from ... and provides
		specific features and event handling mechanisms for PlayerView.
	"""

	def __init__(self, model, view):
                """
                    The constructor for PlayerController initialises the main variabels needed to run.
                    
                    @param model: The model belonging to this GUI.
                    @param view: The view where events are handled from.
                """

		CommonController.__init__(self, model, view)

		helpVis = self.GetTextFromFile(os.path.join(self.programPath, "res/VisibilityExplanation.txt"))
		self.explanations[self.view.VIS_PANEL] = helpVis
		self.explanations[self.view.REC_PANEL] = "Information about the selected recording."

		self.controller = None

		self.BindEvents()

		Publisher.subscribe(self.BindRecLabel    , "BIND_RECLABEL")
		Publisher.subscribe(self.OnPlayBackUpdate, "PlayBackUpdate")

	def BindEvents(self):
                """
                    This method binds the events to the widgets of PlayerView.
                """

		CommonController.BindEvents(self)

		siPanel = self.view.FindWindowById(self.view.VIS_PANEL)
		rePanel = self.view.FindWindowById(self.view.REC_PANEL)
		siPanel.Bind(wx.EVT_ENTER_WINDOW, self.OnEnterPanel, id = self.view.VIS_PANEL)
		rePanel.Bind(wx.EVT_ENTER_WINDOW, self.OnEnterPanel, id = self.view.REC_PANEL)
		
		self.view.Bind(wx.EVT_CHECKBOX, self.OnHead    , id = self.view.HE_CHECKBOX)
		self.view.Bind(wx.EVT_CHECKBOX, self.OnTumor   , id = self.view.TU_CHECKBOX)
		self.view.Bind(wx.EVT_CHECKBOX, self.OnSafePath, id = self.view.SP_CHECKBOX)

		self.view.Bind(wx.EVT_BUTTON, self.OnPlayStop , id = self.view.PLAY_STOP)
		self.view.Bind(wx.EVT_SCROLL, self.SlideChange, id = self.view.SLIDER)

	# -------------------- Methods for handling events from user input
		
	def OnRecLabelSelection(self, event):
                """
                    Method for handling an incoming event when a label is clicked.
                    
                    @param event: Incoming event from view.
                """

		self.model.DisplayInstrument()
		wrapper = InputWrapper(self.model)

		label = event.GetEventObject()
		recId = int(label.GetName())
		self.view.slider.SetValue(0)
		recording = self.model.GetRecording(recId)
		self.controller = PlayBackController(wrapper, recording)
		
		self.view.EnableRecLabel(label)

	def OnHead(self, event):
                """
                    Method for handling an incoming event when the [Head] checkbox is clicked.
                    
                    @param event: Incoming event from view.
                """

		self.model.SetVisibleGroup("Dicom", event.IsChecked())

	def OnTumor(self, event):
                """
                    Method for handling an incoming event when the [Tumor] checkbox is clicked.
                    
                    @param event: Incoming event from view.
                """

		self.model.SetVisibleGroup("Tumor", event.IsChecked())

	def OnSafePath(self, event):
                """
                    Method for handling an incoming event when the [Safe path] checkbox is clicked.
                    
                    @param event: Incoming event from view.
                """

		self.model.SetVisibleGroup("Path", event.IsChecked())

	def OnPlayStop(self, event):
                """
                    Method for handling an incoming event when the [Play] button is pressed.
                    
                    @param event: Incoming event from view.
                """

		playStopButton = event.GetEventObject()
		if self.controller != None:
			print "PlayStop"
			if self.controller.GetDirection() == 0:
				print "start"
				if self.controller.GetPosition() == self.controller.GetMax() - 1:
					print "reset"
					self.controller.SetPosition(0)
				self.controller.Start()
				playStopButton.SetLabel("||")
			else:
				print "stop"
				self.controller.Stop()
				playStopButton.SetLabel(u"\u25BA")

	def SlideChange(self, event):
                """
                    Method for handling an incoming event when the sliderbar has been scrolled.
                    
                    @param event: Incoming event from view.
                """

		if self.controller != None:
			pos = (self.view.slider.GetValue() / (1.0 * self.view.slider.GetMax())) * self.controller.GetMax()
			self.controller.SetPosition(int(pos))
			self.controller.Step()

	# -------------------- Auxiliary methods

	def GetAboutDoc(self):
                """
                    Method for returning backgroundinformation about HeaDSET PathPlayer.

                    @return: String with information about the program.
                """

                endText = Controller.GetAboutDoc(self)
                aboutTextPath = os.path.join(self.programPath, "res/HeaDSETPlayer.txt")
                resultText = self.GetTextFromFile(aboutTextPath) + "\n" + endText
                return resultText

        # -------------------- Publisher-Subscriber methods

	def BindRecLabel(self, message):
                """
                    Method indicating that a new label has been added to the view and
                    bind the corresponding event to the label.
                    
                    @param message: The data provided to this PlayerController to make
                    the necessary changes possible.
                """

		newLabel = message.data
		newLabel.Bind(wx.EVT_LEFT_DOWN, self.OnRecLabelSelection)

	def OnPlayBackUpdate(self, message):
                """
                    Method indicating that an update is needed according to the playback
                    of a current recording.
                    
                    @param message: The data provided to this PlayerController to make
                    the necessary changes possible.
                """

		print message.data * 100, "%", message.data * self.view.slider.GetMax()
		if message.data == 1:
			playStopButton = self.view.FindWindowById(self.view.PLAY_STOP)
			playStopButton.SetLabel(u"\u25BA")
		self.view.slider.SetValue(message.data * self.view.slider.GetMax())

# -------------------- End of class PlayerController



# ==================== Dialog classes

class PropertiesDialog(wx.Dialog):
        """
		PropertiesDialog is an own implementation for a Dialog window to show
		properties information of a current project.
	"""

	# -------------------- Dialog layout constants

	WIDTH  = 500
	HEIGHT = WIDTH / 2.5

	BUTTON_WIDTH = 75
	OK_BUTTON    = 1000

	def __init__(self, parent, id, title, model):
                """
                    The constructor for PropertiesDialog initialises the dialog window with
                    labels containing information about the current project.
                    
                    @param parent: The parent window to which this dialog belongs.
                    @param id: The id given to the Dialog window.
                    @param title: The title of the Dialog window.
                    @param model: The model belonging to this GUI.
                """

		wx.Dialog.__init__(self, parent, id, title, size = (PropertiesDialog.WIDTH, PropertiesDialog.HEIGHT))
		self.SetIcon(parent.GetIcon())

		dlgSizer = wx.BoxSizer(wx.VERTICAL)
                dirDicom, patientName = self.GetDICOMInfo(model.GetDicomModel())

		nrOfTumors = str(model.GetNumberOfTumorGeoms())
		nrOfPath   = str(model.GetNumberOfPathGeoms())

		dicomLabel    = wx.StaticText(self, -1, "Directory of DICOM files: " + dirDicom)
		tumorLabel    = wx.StaticText(self, -1, "Number of tumor primitives: " + nrOfTumors)
		safepathLabel = wx.StaticText(self, -1, "Number of safe path primitives: " + nrOfPath)
		patientLabel  = wx.StaticText(self, -1, "Patientname: " + patientName)

		okButton = wx.Button(self, PropertiesDialog.OK_BUTTON, "OK", size = (PropertiesDialog.BUTTON_WIDTH, -1))
		self.Bind(wx.EVT_BUTTON, self.OnOk, id = PropertiesDialog.OK_BUTTON)

		dlgSizer.Add(dicomLabel   , 0, wx.TOP | wx.LEFT | wx.RIGHT, 10)
		dlgSizer.Add(tumorLabel   , 0, wx.TOP | wx.LEFT | wx.RIGHT, 10)
		dlgSizer.Add(safepathLabel, 0, wx.ALL, 10)
		dlgSizer.Add(wx.StaticLine(self), 0, wx.ALIGN_CENTER | wx.EXPAND)

		dlgSizer.Add(patientLabel, 0, wx.ALL, 10)
		dlgSizer.AddSpacer((PropertiesDialog.WIDTH, 10))
		dlgSizer.Add(okButton, 0, wx.ALIGN_RIGHT | wx.ALL, 10)
		
		self.SetSizer(dlgSizer)

	def GetDICOMInfo(self, dicom):
                """
                    Method for retrieving information from the DICOM image, if any is imported.
                    
                    @param dicom: Any DICOM model that has been imported.
                    @return: Tuple with two Strings, containing the directory path and the patient name.
                """

                dirAndName = ("None", "None")
                if dicom != None:
                        dirAndName = (dicom.GetDirectory(), dicom.GetPatientName())
                return dirAndName

        # -------------------- Methods for handling events from the dialog

	def OnOk(self, event):
                """
                    Method for handling an incoming event when the OK button is pressed.
                    In this case close the dialog window.
                    
                    @param event: Incoming event from dialog.
                """

		self.Destroy()

# -------------------- End of class PropertiesDialog



class DicomDialog(wx.Dialog):
        """
		DicomDialog is an own implementation for a Dialog window to show
		an intermediate window for changing properties of loading a 3D model from
		a DICOM image (before the 3D model calculations are made).
	"""

	# -------------------- Dialog layout constants

	WIDTH  = 500
	HEIGHT = WIDTH / 2

	BUTTON_WIDTH = 75
	OK_BUTTON    = 1000

	def __init__(self, parent, id, title):
                """
                    The constructor for DicomDialog initialises the dialog window with
                    input fields for changing properties of loading a 3D model from a DICOM image.
                    
                    @param parent: The parent window to which this dialog belongs.
                    @param id: The id given to the Dialog window.
                    @param title: The title of the Dialog window.
                """

		wx.Dialog.__init__(self, parent, id, title, size = (DicomDialog.WIDTH, DicomDialog.HEIGHT), style = wx.CAPTION)
		self.SetIcon(parent.GetIcon())

		dlgSizer = wx.GridSizer(6, 2)
		
		extractorText = wx.StaticText(self, -1, "Extract (skin: -500, bone: 300, metal: 500):")
		self.__extractorSpin = wx.SpinCtrl(self, -1)
		self.__extractorSpin.SetRange(-500, 500)
		self.__extractorSpin.SetValue(300)
		
		dlgSizer.Add(extractorText, 0, wx.TOP | wx.LEFT, 10)
		dlgSizer.Add(self.__extractorSpin, 0, wx.ALIGN_RIGHT | wx.TOP | wx.RIGHT, 10)
		 
		decimateText = wx.StaticText(self, -1, "Reduction (0 - 100%):")
		self.__decimateSpin = wx.SpinCtrl(self, -1)
		self.__decimateSpin.SetRange(0, 100)
		self.__decimateSpin.SetValue(95)
		
		dlgSizer.Add(decimateText, 0, wx.LEFT, 10)
		dlgSizer.Add(self.__decimateSpin, 0, wx.ALIGN_RIGHT | wx.RIGHT, 10)
		 
		smootherText = wx.StaticText(self, -1, "Smoother iterations (0 - 20):")
		self.__smootherSpin = wx.SpinCtrl(self, -1)
		self.__smootherSpin.SetRange(0, 50)
		self.__smootherSpin.SetValue(20)
		
		dlgSizer.Add(smootherText, 0, wx.LEFT, 10)
		dlgSizer.Add(self.__smootherSpin, 0, wx.ALIGN_RIGHT | wx.RIGHT, 10)
		
		colorText = wx.StaticText(self, -1, "Color:")
		self.colors = [(239 / 256.0, 208 / 256.0, 207 / 256.0), (0.9, 0.9, 0.9)]
		colorsName = ["Skin", "Bone"]
		self.colorCombo = wx.ComboBox(self, -1, choices = colorsName, style = wx.CB_READONLY)
		self.colorCombo.SetStringSelection("Bone")
		
		dlgSizer.Add(colorText, 0, wx.LEFT, 10)
		dlgSizer.Add(self.colorCombo, 0, wx.ALIGN_RIGHT | wx.RIGHT, 10)

		opacityText = wx.StaticText(self, -1, "Opacity (0 - 100%):")
		self.__opacitySpin = wx.SpinCtrl(self, -1)
		self.__opacitySpin.SetRange(0, 100)
		self.__opacitySpin.SetValue(100)
		
		dlgSizer.Add(opacityText, 0, wx.LEFT, 10)
		dlgSizer.Add(self.__opacitySpin, 0, wx.ALIGN_RIGHT | wx.RIGHT, 10)		
		
		okButton = wx.Button(self, DicomDialog.OK_BUTTON, "OK", size = (DicomDialog.BUTTON_WIDTH, -1))
		dlgSizer.Add(okButton, 0, wx.ALIGN_RIGHT | wx.ALL, 10)
		self.Bind(wx.EVT_BUTTON, self.OnOk, id = DicomDialog.OK_BUTTON)
		self.Centre()
		self.SetSizer(dlgSizer)

	def GetExtraction(self):
                """
                    Method for getting the extraction value from the dialog window.

                    @return: Integer value of extraction.
                """

		return int(self.__extractorSpin.GetValue())
		
	def GetDecimation(self):
                """
                    Method for getting the decimation value from the dialog window.

                    @return: Floating point value of decimation.
                """

		return self.__decimateSpin.GetValue() / 100.0
		
	def GetSmoothing(self):
                """
                    Method for getting the smoothing value from the dialog window.

                    @return: Integer value of smoothing.
                """

		return int(self.__smootherSpin.GetValue())
		
	def GetColor(self):
                """
                    Method for getting the colour value from the dialog window.

                    @return: The selected colour.
                """

		item = self.colorCombo.GetSelection()
		return self.colors[item]
		
	def GetOpacity(self):
                """
                    Method for getting the opacity value from the dialog window.

                    @return: Floating point value of opacity.
                """

		return self.__opacitySpin.GetValue() / 100.0

        # -------------------- Methods for handling events from the dialog

	def OnOk(self, event):
                """
                    Method for handling an incoming event when the OK button is pressed.
                    In this case hide the dialog window.
                    
                    @param event: Incoming event from dialog.
                """

		self.MakeModal(False)
		self.Show(False)

# -------------------- End of class DicomDialog
